﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace Gmantis.Controls.Chart
{
    [EditorBrowsable(EditorBrowsableState.Never)]
    public interface IPlotElement
    {
        // Methods
        bool IsCompatible(IRenderer rend);
        bool Render(RenderContext rc);
        void SetShape(ShapeStyle shapeStyle);

        // Properties
        bool IsClustered { get; }
        UIElement Label { get; set; }
        Shape LegendShape { get; }
        Style Style { get; set; }
    }

    public enum TransformMode
    {
        Relative,
        Absolute
    }

    public class PlotElement : Path, IPlotElement
    {
        // Fields
        private Point _center = new Point();
        private Rect _rect = new Rect();
        private Shape _shape;
        private Size _sz = new Size(10.0, 10.0);
        private TransformMode _tmode;
        protected ShapeStyle autoStyle;
        public static readonly DependencyProperty DataPointProperty = Utils.RegisterProperty("DataPoint", typeof(DataPoint), typeof(PlotElement), new PropertyChangedCallback(PlotElement.OnChanged));
        protected List<UIElement> effects;
        public static readonly DependencyProperty LabelAlignmentProperty = DependencyProperty.RegisterAttached("LabelAlignment", typeof(LabelAlignment), typeof(PlotElement), new PropertyMetadata(LabelAlignment.Auto));
        public static readonly DependencyProperty LabelLineProperty = DependencyProperty.RegisterAttached("LabelLine", typeof(Line), typeof(PlotElement), null);
        public static readonly DependencyProperty LabelOffsetProperty = DependencyProperty.RegisterAttached("LabelOffset", typeof(Point), typeof(PlotElement), new PropertyMetadata(new Point(0.0, 0.0)));
        internal static readonly DependencyProperty LabelProperty = Utils.RegisterProperty("Label", typeof(UIElement), typeof(PlotElement), new PropertyChangedCallback(PlotElement.OnChanged), null);
        protected bool m_isFilled = true;

        // Events
        internal event EventHandler AfterLoaded;

        public event EventHandler Created;

        public event RoutedEventHandler Loaded;

        public event MouseEventHandler MouseEnter;

        public event MouseEventHandler MouseLeave;

        public event MouseButtonEventHandler MouseLeftButtonDown;

        public event MouseButtonEventHandler MouseLeftButtonUp;

        // Methods
        internal PlotElement()
        {
            this.Shape = new Path();
            base.Loaded += new RoutedEventHandler(this.FireLoaded);
            base.MouseEnter += new MouseEventHandler(this.FireMouseEnter);
            base.MouseLeave += new MouseEventHandler(this.FireMouseLeave);
            base.MouseLeftButtonDown += new MouseButtonEventHandler(this.FireMouseLeftButtonDown);
            base.MouseLeftButtonUp += new MouseButtonEventHandler(this.FireMouseLeftButtonUp);
        }

        protected void AdjustLegendShape(Shape shape)
        {
            if (shape != null)
            {
                if ((this.Size.Width <= 14.0) && (this.Size.Width > 0.0))
                {
                    shape.Width = this.Size.Width;
                }
                if ((this.Size.Height <= 14.0) && (this.Size.Height > 0.0))
                {
                    shape.Height = this.Size.Height;
                }
            }
        }

        protected virtual LabelAlignment AutoPosition(Size labelSize, ref Point hot, ref Point offset)
        {
            return LabelAlignment.TopCenter;
        }

        bool IPlotElement.IsCompatible(IRenderer rend)
        {
            return this.IsCompatible(rend);
        }

        bool IPlotElement.Render(RenderContext rc)
        {
            return this.Render(rc);
        }

        void IPlotElement.SetShape(ShapeStyle ss)
        {
            if ((this.autoStyle == null) && (ss != null))
            {
                this.autoStyle = ss;
                PieSlice slice = this as PieSlice;
                Point center = (slice != null) ? slice._pi.Center : new Point(double.NaN, double.NaN);
                Rect bounds = (slice != null) ? slice.geometry.Bounds : Extensions.EmptyRect;
                double radius = (slice != null) ? slice._pi.RadiusX : double.NaN;
                this.autoStyle.Apply(this, this.Shape, center, bounds, radius);
            }
        }

        internal virtual object Clone()
        {
            PlotElement clone = new PlotElement();
            this.CloneAttributes(clone);
            return clone;
        }

        protected void CloneAttributes(PlotElement clone)
        {
            if (base.Fill != null)
            {
                clone.Fill = Utils.Clone(base.Fill);
            }
            if (base.Stroke != null)
            {
                clone.Stroke = Utils.Clone(base.Stroke);
            }
            clone.StrokeThickness = base.StrokeThickness;
            clone.Size = this.Size;
            clone.IsHitTestVisible = base.IsHitTestVisible;
            clone.Style = base.Style;
            clone.Loaded += new RoutedEventHandler(this.FireLoaded);
            clone.MouseEnter += new MouseEventHandler(this.FireMouseEnter);
            clone.MouseLeave += new MouseEventHandler(this.FireMouseLeave);
            clone.MouseLeftButtonDown += new MouseButtonEventHandler(this.FireMouseLeftButtonDown);
            clone.MouseLeftButtonUp += new MouseButtonEventHandler(this.FireMouseLeftButtonUp);
            if (this.Created != null)
            {
                this.Created(clone, EventArgs.Empty);
            }
        }

        private void fe_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            if ((e.NewSize.Width > 0.0) && (e.NewSize.Height > 0.0))
            {
                this.UpdateLabelPosition();
            }
        }

        private void FireLoaded(object sender, RoutedEventArgs e)
        {
            if (this.Loaded != null)
            {
                this.Loaded(sender, e);
            }
            DataSeries series = (this.DataPoint != null) ? this.DataPoint.Series : null;
            if (series != null)
            {
                series.FireLoaded(this, EventArgs.Empty);
            }
            if (this.AfterLoaded != null)
            {
                this.AfterLoaded(this, EventArgs.Empty);
            }
        }

        private void FireMouseEnter(object sender, MouseEventArgs e)
        {
            if (this.MouseEnter != null)
            {
                this.MouseEnter(sender, e);
            }
        }

        private void FireMouseLeave(object sender, MouseEventArgs e)
        {
            if (this.MouseLeave != null)
            {
                this.MouseLeave(sender, e);
            }
        }

        private void FireMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (this.MouseLeftButtonDown != null)
            {
                this.MouseLeftButtonDown(sender, e);
            }
        }

        private void FireMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (this.MouseLeftButtonUp != null)
            {
                this.MouseLeftButtonUp(sender, e);
            }
        }

        internal void FirePlotElementLoaded(object sender, RoutedEventArgs e)
        {
            DataSeries series = (this.DataPoint != null) ? this.DataPoint.Series : null;
            if (series != null)
            {
                series.FirePlotElementLoaded(this, EventArgs.Empty);
            }
        }

        public static LabelAlignment GetLabelAlignment(DependencyObject obj)
        {
            return (LabelAlignment)obj.GetValue(LabelAlignmentProperty);
        }

        public static Line GetLabelLine(DependencyObject obj)
        {
            return (Line)obj.GetValue(LabelLineProperty);
        }

        public static Point GetLabelOffset(DependencyObject obj)
        {
            return (Point)obj.GetValue(LabelOffsetProperty);
        }

        protected virtual bool IsCompatible(IRenderer rend)
        {
            return (rend is Renderer2D);
        }

        private static void OnChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
        }

        protected virtual bool Render(RenderContext rc)
        {
            this._shape.Width = this.Size.Width;
            this._shape.Height = this.Size.Height;
            Canvas.SetLeft(this, rc.Current.X - (0.5 * this._shape.Width));
            Canvas.SetTop(this, rc.Current.Y - (0.5 * this._shape.Height));
            return true;
        }

        public static void SetLabelAlignment(DependencyObject obj, LabelAlignment value)
        {
            obj.SetValue(LabelAlignmentProperty, value);
        }

        public static void SetLabelLine(DependencyObject obj, Line line)
        {
            obj.SetValue(LabelLineProperty, line);
        }

        public static void SetLabelOffset(DependencyObject obj, Point offset)
        {
            obj.SetValue(LabelOffsetProperty, offset);
        }

        internal static PlotElement SymbolFromMarker(Marker marker)
        {
            switch (marker)
            {
                case Marker.Box:
                    return new BoxSymbol();

                case Marker.Dot:
                    return new DotSymbol();

                case Marker.Diamond:
                    {
                        BoxSymbol symbol = new BoxSymbol();
                        RotateTransform transform = new RotateTransform
                        {
                            Angle = 45.0
                        };
                        symbol.RenderTransform = transform;
                        symbol.RenderTransformOrigin = new Point(0.5, 0.5);
                        return symbol;
                    }
                case Marker.Triangle:
                    {
                        RPolygon polygon = new RPolygon();
                        RotateTransform transform2 = new RotateTransform
                        {
                            Angle = -90.0
                        };
                        polygon.RenderTransform = transform2;
                        polygon.RenderTransformOrigin = new Point(0.5, 0.5);
                        return polygon;
                    }
                case Marker.Star4:
                    return new Star { InnerRadius = 0.4 };

                case Marker.Star8:
                    return new Star { NumVertices = 8, InnerRadius = 0.4 };

                case Marker.Cross:
                    return new Star { InnerRadius = 0.0 };

                case Marker.DiagonalCross:
                    {
                        Star star4 = new Star();
                        RotateTransform transform3 = new RotateTransform
                        {
                            Angle = 45.0
                        };
                        star4.RenderTransform = transform3;
                        star4.RenderTransformOrigin = new Point(0.5, 0.5);
                        star4.InnerRadius = 0.0;
                        return star4;
                    }
            }
            return null;
        }

        internal virtual void UpdateLabelPosition()
        {
            if ((this.Label != null) && (base.Parent is Canvas))
            {
                FrameworkElement label = this.Label as FrameworkElement;
                Size labelSize = new Size(label.ActualWidth, label.ActualHeight);
                Rect labelRect = this.LabelRect;
                double left = Canvas.GetLeft(this);
                double top = Canvas.GetTop(this);
                if (!double.IsNaN(left))
                {
                    labelRect.X += left;
                }
                if (!double.IsNaN(top))
                {
                    labelRect.Y += top;
                }
                double naN = double.NaN;
                double d = double.NaN;
                LabelAlignment labelAlignment = GetLabelAlignment(this.Label);
                Point labelOffset = GetLabelOffset(this.Label);
                Point hot = new Point();
                if (labelAlignment == LabelAlignment.Auto)
                {
                    labelAlignment = this.AutoPosition(labelSize, ref hot, ref labelOffset);
                }
                switch (labelAlignment)
                {
                    case LabelAlignment.BottomCenter:
                        naN = (labelRect.X + (labelRect.Width * 0.5)) - (labelSize.Width * 0.5);
                        d = labelRect.Bottom;
                        hot = new Point(labelRect.X + (labelRect.Width * 0.5), labelRect.Bottom);
                        break;

                    case LabelAlignment.BottomLeft:
                        naN = labelRect.X - labelSize.Width;
                        d = labelRect.Bottom;
                        hot = new Point(labelRect.Left, labelRect.Bottom);
                        break;

                    case LabelAlignment.BottomRight:
                        naN = labelRect.Right;
                        d = labelRect.Bottom;
                        hot = new Point(labelRect.Right, labelRect.Bottom);
                        break;

                    case LabelAlignment.MiddleCenter:
                        naN = (labelRect.X + (labelRect.Width * 0.5)) - (labelSize.Width * 0.5);
                        d = (labelRect.Y + (labelRect.Height * 0.5)) - (labelSize.Height * 0.5);
                        hot = new Point(labelRect.X + (labelRect.Width * 0.5), labelRect.Y + (labelRect.Height * 0.5));
                        break;

                    case LabelAlignment.MiddleLeft:
                        naN = labelRect.X - labelSize.Width;
                        d = (labelRect.Y + (labelRect.Height * 0.5)) - (labelSize.Height * 0.5);
                        hot = new Point(labelRect.X, labelRect.Y + (labelRect.Height * 0.5));
                        break;

                    case LabelAlignment.MiddleRight:
                        naN = labelRect.Right;
                        d = (labelRect.Y + (labelRect.Height * 0.5)) - (labelSize.Height * 0.5);
                        hot = new Point(labelRect.Right, labelRect.Y + (labelRect.Height * 0.5));
                        break;

                    case LabelAlignment.TopCenter:
                        naN = (labelRect.X + (labelRect.Width * 0.5)) - (labelSize.Width * 0.5);
                        d = labelRect.Top - labelSize.Height;
                        hot = new Point(labelRect.X + (labelRect.Width * 0.5), labelRect.Top);
                        break;

                    case LabelAlignment.TopLeft:
                        naN = labelRect.X - labelSize.Width;
                        d = labelRect.Top - labelSize.Height;
                        hot = new Point(labelRect.Left, labelRect.Top);
                        break;

                    case LabelAlignment.TopRight:
                        naN = labelRect.Right;
                        d = labelRect.Top - labelSize.Height;
                        hot = new Point(labelRect.Right, labelRect.Top);
                        break;
                }
                if (this.DataPoint != null)
                {
                    this.DataPoint.Point = hot;
                }
                if ((labelOffset.X != 0.0) || (labelOffset.Y != 0.0))
                {
                    Line labelLine = GetLabelLine(this.Label);
                    if (labelLine != null)
                    {
                        labelLine.X1 = hot.X;
                        labelLine.Y1 = hot.Y;
                    }
                    naN += labelOffset.X;
                    d += labelOffset.Y;
                    if (labelLine != null)
                    {
                        labelLine.X2 = hot.X + labelOffset.X;
                        labelLine.Y2 = hot.Y + labelOffset.Y;
                    }
                    this.DataPoint.LabelPoint = new Point(hot.X + labelOffset.X, hot.Y + labelOffset.Y);
                }
                if ((!double.IsNaN(naN) && !double.IsInfinity(naN)) && (!double.IsNaN(d) && !double.IsInfinity(d)))
                {
                    Canvas.SetLeft(label, naN);
                    Canvas.SetTop(label, d);
                }
                else
                {
                    label.Visibility = Visibility.Collapsed;
                }
            }
        }

        internal static void UpdateLabelPosition(PlotElement pe, FrameworkElement lbl)
        {
            if ((pe != null) && (lbl != null))
            {
                Size labelSize = Utils.GetSize(lbl);
                Rect labelRect = pe.LabelRect;
                double left = Canvas.GetLeft(pe);
                double top = Canvas.GetTop(pe);
                if (!double.IsNaN(left))
                {
                    labelRect.X += left;
                }
                if (!double.IsNaN(top))
                {
                    labelRect.Y += top;
                }
                double naN = double.NaN;
                double length = double.NaN;
                LabelAlignment labelAlignment = GetLabelAlignment(lbl);
                Point labelOffset = GetLabelOffset(lbl);
                Point hot = new Point();
                if (labelAlignment == LabelAlignment.Auto)
                {
                    labelAlignment = pe.AutoPosition(labelSize, ref hot, ref labelOffset);
                }
                switch (labelAlignment)
                {
                    case LabelAlignment.BottomCenter:
                        naN = (labelRect.X + (labelRect.Width * 0.5)) - (labelSize.Width * 0.5);
                        length = labelRect.Bottom;
                        hot = new Point(labelRect.X + (labelRect.Width * 0.5), labelRect.Bottom);
                        break;

                    case LabelAlignment.BottomLeft:
                        naN = labelRect.X - labelSize.Width;
                        length = labelRect.Bottom;
                        hot = new Point(labelRect.Left, labelRect.Bottom);
                        break;

                    case LabelAlignment.BottomRight:
                        naN = labelRect.Right;
                        length = labelRect.Bottom;
                        hot = new Point(labelRect.Right, labelRect.Bottom);
                        break;

                    case LabelAlignment.MiddleCenter:
                        naN = (labelRect.X + (labelRect.Width * 0.5)) - (labelSize.Width * 0.5);
                        length = (labelRect.Y + (labelRect.Height * 0.5)) - (labelSize.Height * 0.5);
                        hot = new Point(labelRect.X + (labelRect.Width * 0.5), labelRect.Y + (labelRect.Height * 0.5));
                        break;

                    case LabelAlignment.MiddleLeft:
                        naN = labelRect.X - labelSize.Width;
                        length = (labelRect.Y + (labelRect.Height * 0.5)) - (labelSize.Height * 0.5);
                        hot = new Point(labelRect.X, labelRect.Y + (labelRect.Height * 0.5));
                        break;

                    case LabelAlignment.MiddleRight:
                        naN = labelRect.Right;
                        length = (labelRect.Y + (labelRect.Height * 0.5)) - (labelSize.Height * 0.5);
                        hot = new Point(labelRect.Right, labelRect.Y + (labelRect.Height * 0.5));
                        break;

                    case LabelAlignment.TopCenter:
                        naN = (labelRect.X + (labelRect.Width * 0.5)) - (labelSize.Width * 0.5);
                        length = labelRect.Top - labelSize.Height;
                        hot = new Point(labelRect.X + (labelRect.Width * 0.5), labelRect.Top);
                        break;

                    case LabelAlignment.TopLeft:
                        naN = labelRect.X - labelSize.Width;
                        length = labelRect.Top - labelSize.Height;
                        hot = new Point(labelRect.Left, labelRect.Top);
                        break;

                    case LabelAlignment.TopRight:
                        naN = labelRect.Right;
                        length = labelRect.Top - labelSize.Height;
                        hot = new Point(labelRect.Right, labelRect.Top);
                        break;
                }
                if ((labelOffset.X != 0.0) || (labelOffset.Y != 0.0))
                {
                    Line labelLine = GetLabelLine(lbl);
                    if (labelLine != null)
                    {
                        labelLine.X1 = hot.X;
                        labelLine.Y1 = hot.Y;
                    }
                    naN += labelOffset.X;
                    length += labelOffset.Y;
                    if (labelLine != null)
                    {
                        labelLine.X2 = hot.X + labelOffset.X;
                        labelLine.Y2 = hot.Y + labelOffset.Y;
                    }
                }
                Canvas.SetLeft(lbl, naN);
                Canvas.SetTop(lbl, length);
            }
        }

        // Properties
        internal ShapeStyle AutoStyle
        {
            get
            {
                return this.autoStyle;
            }
        }

        bool IPlotElement.IsClustered
        {
            get
            {
                return this.IsClustered;
            }
        }

        UIElement IPlotElement.Label
        {
            get
            {
                return this.Label;
            }
            set
            {
                this.Label = value;
            }
        }

        Shape IPlotElement.LegendShape
        {
            get
            {
                return this.LegendShape;
            }
        }

        Style IPlotElement.Style
        {
            get
            {
                return base.Style;
            }
            set
            {
                base.Style = value;
            }
        }

        public Point Center
        {
            get
            {
                return this._center;
            }
            internal set
            {
                this._center = value;
            }
        }

        public DataPoint DataPoint
        {
            get
            {
                return (DataPoint)base.GetValue(DataPointProperty);
            }
            internal set
            {
                base.SetValue(DataPointProperty, value);
            }
        }

        protected Shape DefaultLegendShape
        {
            get
            {
                Rectangle rectangle = new Rectangle();
                rectangle.RadiusX = rectangle.RadiusY = 0.0;
                rectangle.Fill = Utils.Clone(base.Fill);
                rectangle.Stroke = Utils.Clone(base.Stroke);
                rectangle.StrokeThickness = base.StrokeThickness;
                return rectangle;
            }
        }

        internal List<UIElement> Effects
        {
            get
            {
                if (this.effects == null)
                {
                    this.effects = new List<UIElement>();
                }
                return this.effects;
            }
        }

        protected virtual bool IsClustered
        {
            get
            {
                return false;
            }
        }

        internal UIElement Label
        {
            get
            {
                return (base.GetValue(LabelProperty) as UIElement);
            }
            set
            {
                base.SetValue(LabelProperty, value);
                FrameworkElement label = this.Label as FrameworkElement;
                if (label != null)
                {
                    label.SizeChanged += new SizeChangedEventHandler(this.fe_SizeChanged);
                }
            }
        }

        internal virtual Rect LabelRect
        {
            get
            {
                Rect rect = new Rect();
                if (this.Shape is Path)
                {
                    return ((Path)this.Shape).Data.Bounds;
                }
                if (this.Shape is Ellipse)
                {
                    Ellipse shape = (Ellipse)this.Shape;
                    return new Rect(0.0, 0.0, shape.Width, shape.Height);
                }
                if (this.Shape is Rectangle)
                {
                    Rectangle rectangle = (Rectangle)this.Shape;
                    rect = new Rect(0.0, 0.0, rectangle.Width, rectangle.Height);
                }
                return rect;
            }
        }

        protected virtual Shape LegendShape
        {
            get
            {
                return this.DefaultLegendShape;
            }
        }

        public Rect PlotRect
        {
            get
            {
                return this._rect;
            }
            internal set
            {
                this._rect = value;
            }
        }

        public Shape Shape
        {
            get
            {
                return this;
            }
            internal set
            {
                this._shape = value;
            }
        }

        public virtual Size Size
        {
            get
            {
                return this._sz;
            }
            set
            {
                this._sz = value;
            }
        }

        public TransformMode TransformMode
        {
            get
            {
                return this._tmode;
            }
            set
            {
                this._tmode = value;
            }
        }
    }
}
